package com.springmvc.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.springmvc.annotation.Controller;
import com.springmvc.annotation.RequestMapping;
import com.springmvc.annotation.RequestParam;
import com.springmvc.annotation.ResponseBody;
import com.springmvc.context.WebApplicationContext;
import com.springmvc.exception.ContextException;
import com.springmvc.handle.MyHandle;
import com.sun.deploy.net.HttpRequest;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by DELL on 2022/4/2
 **/
public class DispatcherServlet extends HttpServlet {

    private WebApplicationContext webAppelicationContext;
    //存储URL和对象的方法映射关系
    public List<MyHandle> handleList = new ArrayList<MyHandle>();

    @Override
    public void init() throws ServletException {
        //1.servlet初始化的时候，读取初始化的参数 classpath: springmvc.xml
        String contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");
        //2.创建spring容器
        webAppelicationContext = new WebApplicationContext(contextConfigLocation);
        //3.初始化spring容器
        webAppelicationContext.refresh();
        //4.初始化请求映射   /user/query --->Controller ---method----parameter
        initHandleMapping();
        System.out.println("请求地址和控制器方法的映射关系:" + handleList);
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //请求的分发处理
        excuteDispatch(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }

    /**
     * 初始化请求映射
     */
    public void initHandleMapping() {
        if (webAppelicationContext.iocMap.isEmpty()) {
            throw new ContextException("Spring容器为空");
        }
        //遍历Spring容器类中的iocmap查找Controller类
        for (Map.Entry<String, Object> entry : webAppelicationContext.iocMap.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(Controller.class)) {
                //遍历controller类查找有requestmapping地址映射的方法，并添加到自定义handle类的list中
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class);
                        String url = annotation.value();
                        //将映射的地址，和controller类的实例，和与映射地址对应的方法添加到自定义handle类，并将这个handle类添加到list中
                        handleList.add(new MyHandle(url, entry.getValue(), declaredMethod));
                    }
                }
            }
        }
    }

    /**
     * 请求的分发处理
     */
    public void excuteDispatch(HttpServletRequest request, HttpServletResponse response) {
        try {
            MyHandle handle = getHandle(request);
            if (handle == null) {
                response.getWriter().print("<h1>404 NOT FOUND!</h1>");
            } else {
                Class<?>[] parameterTypes = handle.getMethod().getParameterTypes();
                //定义一个参数的数组
                Object[] params = new Object[parameterTypes.length];

                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    if (parameterType.getSimpleName().equals("HttpServletRequest")) {
                        params[i] = request;
                    } else if (parameterType.getSimpleName().equals("HttpServletResponse")) {
                        params[i] = response;
                    }
                }
                //获取请求中的参数的集合
                Map<String, String[]> parameterMap = request.getParameterMap();
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    //请求的参数和值
                    String name = entry.getKey();
                    String value = entry.getValue()[0];
                    System.out.println("请求参数: " + name + "-----" + value);
                    int i = hasRequestParam(handle.getMethod(), name);
                    if (i > 0) {
                        params[i] = value;
                    } else {
                        //如果@RequestParam的值匹配不到，就匹配参数名称和请求名称谁否相等
                        List<String> parameterNames = getParameterNames(handle.getMethod());
                        System.out.println(parameterNames);
                        for (int j = 0; j < parameterNames.size(); j++) {
                            if (parameterNames.get(j).equals(name)) {
                                params[j] = value;
                                break;
                            }
                        }
                    }
                }
                Object result = handle.getMethod().invoke(handle.getController(), params);
                if (result instanceof String) {
                    //跳转jsp
                    String viewName = String.valueOf(result);
                    if (viewName.contains(":")) {
                        String viewType = viewName.split(":")[0];
                        String viewPage = viewName.split(":")[1];
                        if (viewType.equals("forward")) {
                            System.out.println(viewPage);
                            request.getRequestDispatcher(viewPage).forward(request, response);
                        } else if (viewType.equals("redirect")) {
                            response.sendRedirect(viewPage);
                        }
                    } else {
                        //默认转发(只有页面名字，就是viewName)
                        System.out.println(viewName);
                        request.getRequestDispatcher(viewName).forward(request, response);
                    }
                } else {
                    //返回JSON数据
                    Method method = handle.getMethod();
                    if (method.isAnnotationPresent(ResponseBody.class)) {
                        //把返回值调用json转换工具转换为json字符串
                        ObjectMapper mapper = new ObjectMapper();
                        String jsonString = mapper.writeValueAsString(result);
                        response.setContentType("text/html;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        out.print(jsonString);
                        out.flush();
                        out.close();
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public List<String> getParameterNames(Method method) {
        List<String> list = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            list.add(parameter.getName());
        }
        return list;
    }

    /**
     * 获取请求对应的handle
     *
     * @param request
     * @return
     */
    public MyHandle getHandle(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        for (MyHandle handle : handleList) {
            if (handle.getUrl().equals(requestURI)) {
                return handle;
            }
        }
        return null;
    }

    /**
     * 判断控制器方法的参数是否有requestparam注解，且找到对应的value值,如果找到，就返回这个参数的位置，否则返回-1
     *
     * @param method
     * @param name
     * @return
     */
    public int hasRequestParam(Method method, String name) {
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].isAnnotationPresent(RequestParam.class)) {
                RequestParam annotation = parameters[i].getAnnotation(RequestParam.class);
                String value = annotation.value();
                if (value.equals(name)) {
                    return i;
                }
            }
        }
        return -1;
    }
}
